﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Linq;


namespace Devtm.ServiceModel.Discovery.Collection
{



    public class TableCollection
    {
        protected internal static string GetNameProperty<T1, T2>(Expression<Func<T1, T2>> k)
        {

            if (k.Body.NodeType == ExpressionType.MemberAccess)
            {
                MemberExpression m1 = (MemberExpression)k.Body;
                return m1.Member.Name;
            }
            if (k.Body.NodeType == ExpressionType.Call)
            {
                MethodCallExpression m2 = (MethodCallExpression)k.Body;
                return m2.Method.Name;
            }
            else
            {
                throw new NotImplementedException("GetNameProperty : " + k.NodeType.ToString());
            }

        }

    }


    /// <summary>
    /// Observable Collection based on hashtable
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TKey">The type of the key.</typeparam>
    [Serializable()]
    //[DebuggerNonUserCodeAttribute()]
    public class TableCollection<T, TKey> : ITableCollection<T, TKey>
    {
        // System.Collections.DictionaryEntry

        #region private

        //private ObservableCollection<string> yy;1
        protected internal Hashtable _Lst = new Hashtable();
        internal Func<T, TKey> FunctionKey;
        public Expression<Func<T, TKey>> FieldExpression { get; set; }
        private bool objectIsKey = false;

        private Monitoring _monitor;
        //Enumerator<T, TKey> myEnum;
        public string NameProperty { get; set; }

        internal List<TKey> bucketkey = new List<TKey>();

        #endregion

        #region ctor

        protected TableCollection(Expression<Func<T, TKey>> key, TableCollection<T, TKey> parent)
            : this(key)
        {
            _Lst = parent._Lst;

            bucketkey = parent.bucketkey;

        }

        protected TableCollection(TableCollection<T, TKey> parent)
            : this()
        {
            _Lst = parent._Lst;

            bucketkey = parent.bucketkey;

        }

        internal protected TableCollection(Expression<Func<T, TKey>> key, Hashtable hashtable)
            : this(key)
        {
            _Lst = hashtable;

            foreach (TKey k in hashtable.Keys)
                bucketkey.Add(k);

        }

        internal protected TableCollection(Hashtable hashtable)
            : this()
        {
            _Lst = hashtable;

            foreach (TKey k in hashtable.Keys)
                bucketkey.Add(k);
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="TableCollection&lt;T, TKey&gt;"/> class.
        /// </summary>
        /// <param name="key">Lambda expression describs the property contains the key.</param>
        public TableCollection(Expression<Func<T, TKey>> key)
        {
            ThrowIfNotExistOnGet = true;
            FieldExpression = key;
            FunctionKey = key.Compile();
            this._monitor = new Monitoring();
            this.NameProperty = TableCollection.GetNameProperty(key);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TableCollection&lt;T, TKey&gt;"/> class.
        /// </summary>
        /// <param name="key">Lambda expression describs the property contains the key.</param>
        public TableCollection()
        {
            ThrowIfNotExistOnGet = true;
            objectIsKey = true;
            Expression<Func<T, TKey>> key = c => (TKey)(Object)c;
            FieldExpression = key;
            FunctionKey = key.Compile();
            this._monitor = new Monitoring();
            this.NameProperty = "None";
        }

        #endregion

        #region Methodes


        /// <summary>
        /// Adds an element with the specified key of the constructor and value into the <see cref="Hashtable"/>.
        /// return the item
        /// </summary>
        /// <param name="item">The item.</param>
        public virtual T Add(T item)
        {
            TKey k = (TKey)FunctionKey(item);
            if (k == null)
                throw new MethodAccessException("The key return null value. You can't index with null value.");

            lock (_Lst.SyncRoot)
            {
                this.CheckReentrancy();
                _Lst.Add(k, item);
                bucketkey.Add(k);
            }

            this.PropertyChange("Count");
            this.PropertyChange("Item[]");

            if (item is INotifyPropertyChanged)
                (item as INotifyPropertyChanged).PropertyChanged += IndexedObservableCollection_PropertyChanged;

            this.CollectionChange(NotifyCollectionChangedAction.Add, item, _Lst.Count);

            this.LastChange = DateTime.Now;

            return item;
        }

        /// <summary>
        /// Adds an element with the specified key of the constructor and value into the <see cref="Hashtable"/>.
        /// return the item
        /// </summary>
        /// <param name="item">The item.</param>
        public virtual T Add(TKey key, T item)
        {
            
            lock (_Lst.SyncRoot)
            {
                this.CheckReentrancy();
                _Lst.Add(key, item);
                bucketkey.Add(key);
            }

            this.PropertyChange("Count");
            this.PropertyChange("Item[]");

            if (item is INotifyPropertyChanged)
                (item as INotifyPropertyChanged).PropertyChanged += IndexedObservableCollection_PropertyChanged;

            this.CollectionChange(NotifyCollectionChangedAction.Add, item, _Lst.Count);

            this.LastChange = DateTime.Now;

            return item;
        }


        /// <summary>
        /// Adds the elements range.
        /// </summary>
        /// <param name="items">The items.</param>
        public virtual void AddList(IList<T> items)
        {

            foreach (T item in items)
            {

                TKey k = (TKey)FunctionKey.Invoke(item);
                lock (_Lst.SyncRoot)
                {
                    this.CheckReentrancy();
                    _Lst.Add(k, item);
                    bucketkey.Add(k);
                }

                if (item is INotifyPropertyChanged)
                    (item as INotifyPropertyChanged).PropertyChanged += IndexedObservableCollection_PropertyChanged;

            }

            this.PropertyChange("Count");
            this.PropertyChange("Item[]");

            this.LastChange = DateTime.Now;

            this.CollectionChange(NotifyCollectionChangedAction.Add, items);

        }


        /// <summary>
        /// Removes the specified item.
        /// the key is the lambda of the contructor
        /// </summary>
        /// <param name="item">The item.</param>
        /// <exception cref="ArgumentNullException">the key is null</exception>
        /// <exception cref="NotSupportedException">the hashtable is read only</exception>
        public virtual void Remove(T item)
        {

            TKey k = (TKey)FunctionKey.Invoke(item);
            this.CheckReentrancy();

            int index = GetIndexKey(k);
            lock (_Lst.SyncRoot)
            {
                _Lst.Remove(k);
                bucketkey.Remove(k);
            }
            this.PropertyChange("Count");
            this.PropertyChange("Item[]");

            if (item is INotifyPropertyChanged)
                (item as INotifyPropertyChanged).PropertyChanged -= IndexedObservableCollection_PropertyChanged;

            this.LastChange = DateTime.Now;

            this.CollectionChange(NotifyCollectionChangedAction.Remove, item, index);
        }


        private int GetIndexKey(TKey k)
        {

            return bucketkey.IndexOf(k);

        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public virtual void RemoveKey(TKey key)
        {
            T item = this[key];
            Remove(this[key]);

        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public virtual bool TryRemoveKey(TKey key)
        {
            T item = this[key];
            if (ContainsKey(key))
            {
                Remove(this[key]);
                return true;
            }

            return false;

        }


        /// <summary>
        /// Removes the item specified by the key and return the dettached element.
        /// </summary>
        /// <param name="key">The key.</param>
        public virtual bool TryRemove(T item)
        {
            if (Contains(item))
            {
                Remove(item);
                return true;
            }

            return false;

        }

        public virtual T Detach(TKey key)
        {
            T item = this[key];
            Remove(this[key]);
            return item;
        }


        /// <summary>
        /// Remplace l'élément à l'index spécifié.
        /// </summary>
        /// <param name="item">
        /// Nouvelle valeur de l'élément à l'index spécifié dans l'item
        /// </param>
        public virtual void Update(T item)
        {
            TKey k = (TKey)FunctionKey.Invoke(item);

            T oldItem = default(T);
            lock (_Lst.SyncRoot)
            {
                this.CheckReentrancy();
                oldItem = (T)_Lst[k];

                if (item is INotifyPropertyChanged)
                    (item as INotifyPropertyChanged).PropertyChanged -= IndexedObservableCollection_PropertyChanged;

                if (oldItem.Equals(item))
                    _Lst[k] = item;

                if (item is INotifyPropertyChanged)
                    (item as INotifyPropertyChanged).PropertyChanged += IndexedObservableCollection_PropertyChanged;
            }

            this.LastChange = DateTime.Now;

            this.PropertyChange("Item[]");
            this.CollectionChange(NotifyCollectionChangedAction.Replace, oldItem, item);
        }



        /// <summary>
        /// Supprime tous les éléments de la collection.
        /// </summary>
        public virtual void Clear()
        {

            lock (_Lst.SyncRoot)
            {
                this.CheckReentrancy();

                foreach (var item in this)
                    if (item is INotifyPropertyChanged)
                        (item as INotifyPropertyChanged).PropertyChanged -= IndexedObservableCollection_PropertyChanged;
            }

            _Lst.Clear();
            bucketkey.Clear();
            this.LastChange = DateTime.Now;
            this.PropertyChange("Count");
            this.PropertyChange("Item[]");
            this.OnCollectionReset();
        }


        /// <summary>
        /// Determines whether the specified key contains key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if the specified key contains key; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsKey(TKey key) { return _Lst.ContainsKey(key); }


        /// <summary>
        /// Determines whether the specified item contains the key from the item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>
        /// 	<c>true</c> if the specified item contains key; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool ContainsKeyFrom(T item)
        {
            TKey key = (TKey)FunctionKey.Invoke(item);
            return _Lst.ContainsKey(key);
        }



        /// <summary>
        /// Determines whether [contains] [the specified key].
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>
        /// 	<c>true</c> if [contains] [the specified key]; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool Contains(T item) { return _Lst.ContainsValue(item); }



        internal void CopyValues(T[] array, int arrayIndex)
        {
            List<TKey> buckets = this.bucketkey;
            int length = buckets.Count;
            while (--length >= 0)
            {
                TKey key = buckets[length];
                array.SetValue(_Lst[key], arrayIndex++);
            }
        }


        /// <summary>
        /// Syncs the root.
        /// </summary>
        /// <returns></returns>
        public Object SyncRoot() { return _Lst.SyncRoot; }


        /// <summary>
        /// Determines whether this instance is synchronized.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if this instance is synchronized; otherwise, <c>false</c>.
        /// </returns>
        public bool IsSynchronized() { return _Lst.IsSynchronized; }


        /// <summary>
        /// Keys this instance.
        /// </summary>
        /// <returns></returns>
        public ICollection Keys() { return (ICollection)_Lst.Keys; }


        /// <summary>
        /// Values this instance.
        /// </summary>
        /// <returns></returns>
        public ICollection Values() { return (ICollection)_Lst.Values; }


        /// <summary>
        /// Gets the specified <see cref="T"/> item by the key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public virtual T Get(TKey key) { return PrivateGet(key); }


        /// <summary>
        /// Gets the  specified <see cref="T"/> item by the key.
        /// and return a <c>bool</c>
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public virtual bool TryGet(TKey key, out T item)
        {
            item = default(T);

            if (ContainsKey(key))
            {
                item = (T)_Lst[key];
                return true;
            }

            return false;
        }


        /// <summary>
        /// Gets the  specified <see cref="T"/> item by the key.
        /// </summary>
        /// <value></value>
        public virtual T this[TKey key]
        {
            get { return PrivateGet(key); }
            set { Update(value); }
        }       

        private T PrivateGet(TKey key)
        {

            T ret = (T)_Lst[key];

            if (ret != null)
                return ret;

            if (FirstChanceResolveGet != null)
            {
                ResolveGetCollectionEventArgs args = new ResolveGetCollectionEventArgs(key);
                using (this.BlockReentrancy())
                {
                    T u = FirstChanceResolveGet(this, args);
                    if (u != null && args.AddReturn)
                        return this.Add(u);
                }
            }

            if (ThrowIfNotExistOnGet)
                throw new NotExistsException(key);

            return default(T);
        }



        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>The count.</value>
        public int Count { get { return _Lst.Count; } }


        /// <summary>
        /// Gets the key.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public TKey GetKey(T item) { return (TKey)FunctionKey.Invoke(item); }

        #endregion

        #region regul entry

        /// <summary>
        /// Interdit les tentatives réentrantes destinées à modifier cette collection.
        /// </summary>
        /// <returns>
        /// Objet <see cref="T:System.IDisposable" /> qui peut être utilisé pour éliminer l'objet.
        /// </returns>
        protected IDisposable BlockReentrancy()
        {
            this._monitor.Enter();
            return this._monitor;
        }

        /// <summary>
        /// Vérifie les tentatives réentrantes destinées à modifier cette collection.
        /// </summary>
        /// <exception cref="T:System.InvalidOperationException">
        /// S'il y a eu un appel à <see cref="M:System.Collections.ObjectModel.ObservableCollection`1.BlockReentrancy" /> dont la valeur de retour <see cref="T:System.IDisposable" /> n'a pas encore été éliminée. En général, cela signifie qu'il existe d'autres tentatives destinées à modifier cette collection pendant un événement <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" />. Toutefois, cela dépend du moment où les classes dérivées choisissent d'appeler <see cref="M:System.Collections.ObjectModel.ObservableCollection`1.BlockReentrancy" />.
        /// </exception>
        protected void CheckReentrancy()
        {
            if ((this._monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1))
                throw new InvalidOperationException("ObservableCollectionReentrancyNotAllowed");
        }

        #endregion

        #region OnCollectionChanged

        /// <summary>
        /// Déclenche l'événement <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.CollectionChanged" /> avec les arguments fournis.
        /// </summary>
        /// <param name="e">
        /// Arguments de l'événement déclenché.
        /// </param>
        protected virtual void CollectionChange(NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
                using (this.BlockReentrancy())
                    this.CollectionChanged(this, e);
        }

        protected void CollectionChange(NotifyCollectionChangedAction action, T item, int index)
        {
            this.CollectionChange(new NotifyCollectionChangedEventArgs(action, item, index));
        }

        protected void CollectionChange(NotifyCollectionChangedAction action, IList<T> item)
        {
            this.CollectionChange(new NotifyCollectionChangedEventArgs(action, item as IList));
        }

        protected void CollectionChange(NotifyCollectionChangedAction action, T oldItem, object newItem)
        {
            this.CollectionChange(new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
        }

        private void OnCollectionReset()
        {
            this.CollectionChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region OnPropertyChanged

        /// <summary>
        /// Déclenche l'événement <see cref="E:System.Collections.ObjectModel.ObservableCollection`1.PropertyChanged" /> avec les arguments fournis.
        /// </summary>
        /// <param name="e">
        /// Arguments de l'événement déclenché.
        /// </param>
        protected virtual void PropertyChange(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, e);
        }

        protected void PropertyChange(string propertyName)
        {
            this.PropertyChange(new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        void IndexedObservableCollection_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (PropertyItemChanged != null)
                PropertyItemChanged(this,
                    new ItemPropertyChangedEventArgs<T>(e.PropertyName, (T)sender));
        }

        public event ItemPropertyChangedEventHandler<T> PropertyItemChanged;

        public delegate void ItemPropertyChangedEventHandler(object sender, ItemPropertyChangedEventArgs<T> e);

       

        #endregion

        #region Enumerator

        public IEnumerator<T> GetEnumerator() { return new Enumerator<T, TKey>(_Lst, bucketkey); }
        IEnumerator IEnumerable.GetEnumerator() { return new Enumerator<T, TKey>(_Lst, bucketkey); }

        #endregion

        #region Monitor

        [Serializable]
        [DebuggerNonUserCodeAttribute()]
        protected class Monitoring : IDisposable
        {
            private int _busyCount;
            public void Dispose() { this._busyCount--; }
            public void Enter() { this._busyCount++; }
            public bool Busy { get { return (this._busyCount > 0); } }
            public int BusyCount { get { return _busyCount; } }
        }

        public bool IsBusy { get { return _monitor.Busy; } }

        #endregion

        public virtual void Dispose()
        {
            _Lst = null;
            FunctionKey = null;
            _monitor = null;
        }


        public event ResolveGetCollectionEventHandler<T> FirstChanceResolveGet;

        public bool ThrowIfNotExistOnGet { get; set; }


        public DateTime LastChange { get; private set; }

        public bool ObjectIsKey { get { return ObjectIsKey; } }





     
    }

}


